home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 98 / Skunkware 98.iso / src / mail / pine3.96.tar.gz / pine3.96.tar / pine3.96 / imap / ANSI / c-client / mh.c < prev    next >
C/C++ Source or Header  |  1996-05-15  |  44KB  |  1,464 lines

  1. /*
  2.  * Program:    MH mail routines
  3.  *
  4.  * Author(s):    Mark Crispin
  5.  *        Networks and Distributed Computing
  6.  *        Computing & Communications
  7.  *        University of Washington
  8.  *        Administration Building, AG-44
  9.  *        Seattle, WA  98195
  10.  *        Internet: MRC@CAC.Washington.EDU
  11.  *
  12.  * Date:    23 February 1992
  13.  * Last Edited:    15 May 1996
  14.  *
  15.  * Copyright 1996 by the University of Washington
  16.  *
  17.  *  Permission to use, copy, modify, and distribute this software and its
  18.  * documentation for any purpose and without fee is hereby granted, provided
  19.  * that the above copyright notice appears in all copies and that both the
  20.  * above copyright notice and this permission notice appear in supporting
  21.  * documentation, and that the name of the University of Washington not be
  22.  * used in advertising or publicity pertaining to distribution of the software
  23.  * without specific, written prior permission.  This software is made
  24.  * available "as is", and
  25.  * THE UNIVERSITY OF WASHINGTON DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED,
  26.  * WITH REGARD TO THIS SOFTWARE, INCLUDING WITHOUT LIMITATION ALL IMPLIED
  27.  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, AND IN
  28.  * NO EVENT SHALL THE UNIVERSITY OF WASHINGTON BE LIABLE FOR ANY SPECIAL,
  29.  * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
  30.  * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, TORT
  31.  * (INCLUDING NEGLIGENCE) OR STRICT LIABILITY, ARISING OUT OF OR IN CONNECTION
  32.  * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  33.  *
  34.  */
  35.  
  36. #include <stdio.h>
  37. #include <ctype.h>
  38. #include <errno.h>
  39. extern int errno;        /* just in case */
  40. #include "mail.h"
  41. #include "osdep.h"
  42. #include <pwd.h>
  43. #include <sys/stat.h>
  44. #include <sys/time.h>
  45. #include "mh.h"
  46. #include "rfc822.h"
  47. #include "misc.h"
  48. #include "dummy.h"
  49.  
  50. /* MH mail routines */
  51.  
  52.  
  53. /* Driver dispatch used by MAIL */
  54.  
  55. DRIVER mhdriver = {
  56.   "mh",                /* driver name */
  57.   (DRIVER *) NIL,        /* next driver */
  58.   mh_valid,            /* mailbox is valid for us */
  59.   mh_parameters,        /* manipulate parameters */
  60.   mh_find,            /* find mailboxes */
  61.   mh_find_bboards,        /* find bboards */
  62.   mh_find_all,            /* find all mailboxes */
  63.   mh_find_all_bboards,        /* find all bboards */
  64.   mh_subscribe,            /* subscribe to mailbox */
  65.   mh_unsubscribe,        /* unsubscribe from mailbox */
  66.   mh_subscribe_bboard,        /* subscribe to bboard */
  67.   mh_unsubscribe_bboard,    /* unsubscribe from bboard */
  68.   mh_create,            /* create mailbox */
  69.   mh_delete,            /* delete mailbox */
  70.   mh_rename,            /* rename mailbox */
  71.   mh_open,            /* open mailbox */
  72.   mh_close,            /* close mailbox */
  73.   mh_fetchfast,            /* fetch message "fast" attributes */
  74.   mh_fetchflags,        /* fetch message flags */
  75.   mh_fetchstructure,        /* fetch message envelopes */
  76.   mh_fetchheader,        /* fetch message header only */
  77.   mh_fetchtext,            /* fetch message body only */
  78.   mh_fetchbody,            /* fetch message body section */
  79.   mh_setflag,            /* set message flag */
  80.   mh_clearflag,            /* clear message flag */
  81.   mh_search,            /* search for message based on criteria */
  82.   mh_ping,            /* ping mailbox to see if still alive */
  83.   mh_check,            /* check for new messages */
  84.   mh_expunge,            /* expunge deleted messages */
  85.   mh_copy,            /* copy messages to another mailbox */
  86.   mh_move,            /* move messages to another mailbox */
  87.   mh_append,            /* append string message to mailbox */
  88.   mh_gc                /* garbage collect stream */
  89. };
  90.  
  91.                 /* prototype stream */
  92. MAILSTREAM mhproto = {&mhdriver};
  93.  
  94.  
  95. /* MH mail validate mailbox
  96.  * Accepts: mailbox name
  97.  * Returns: our driver if name is valid, NIL otherwise
  98.  */
  99.  
  100. DRIVER *mh_valid (char *name)
  101. {
  102.   char tmp[MAILTMPLEN];
  103.   return mh_isvalid (name,tmp,T) ? &mhdriver : NIL;
  104. }
  105.  
  106. /* MH mail test for valid mailbox
  107.  * Accepts: mailbox name
  108.  *        temporary buffer to use
  109.  *        syntax only test flag
  110.  * Returns: T if valid, NIL otherwise
  111.  */
  112.  
  113. static char *mh_path = NIL;    /* holds MH path name */
  114. static long mh_once = 0;    /* already through this code */
  115.  
  116. int mh_isvalid (char *name,char *tmp,long synonly)
  117. {
  118.   struct stat sbuf;
  119.   if (!mh_path) {        /* have MH path yet? */
  120.     char *s,*s1,*t,*v;
  121.     int fd;
  122.     if (mh_once++) return NIL;    /* only do this code once */
  123.     sprintf (tmp,"%s/%s",myhomedir (),MHPROFILE);
  124.     if ((fd = open (tmp,O_RDONLY,NIL)) < 0) return NIL;
  125.     fstat (fd,&sbuf);        /* yes, get size and read file */
  126.     read (fd,(s1 = t = (char *) fs_get (sbuf.st_size + 1)),sbuf.st_size);
  127.     close (fd);            /* don't need the file any more */
  128.     t[sbuf.st_size] = '\0';    /* tie it off */
  129.                 /* parse profile file */
  130.     while (*(s = t) && (t = strchr (s,'\n'))) {
  131.       *t++ = '\0';        /* tie off line */
  132.                 /* found space in line? */
  133.       if (v = strpbrk (s," \t")) {
  134.     *v = '\0';        /* tie off, is keyword "Path:"? */
  135.     if (!strcmp (lcase (s),"path:")) {
  136.       if (*++v == '/') s = v;
  137.       else sprintf (s = tmp,"%s/%s",myhomedir (),v);
  138.       mh_path = cpystr (s);    /* copy name */
  139.       break;        /* don't need to look at rest of file */
  140.     }
  141.       }
  142.     }
  143.     fs_give ((void **) &s1);    /* flush profile text */
  144.     if (!mh_path) {        /* default path if not in the profile */
  145.       sprintf (tmp,"%s/%s",myhomedir (),MHPATH);
  146.       mh_path = cpystr (tmp);
  147.     }
  148.   }
  149.  
  150.                 /* name must be #MHINBOX or #mh/... */
  151.   if (strcmp (ucase (strcpy (tmp,name)),"#MHINBOX") &&
  152.       !(tmp[0] == '#' && tmp[1] == 'M' && tmp[2] == 'H' && tmp[3] == '/')) {
  153.     errno = EINVAL;        /* bogus name */
  154.     return NIL;
  155.   }
  156.                 /* all done if syntax only check */
  157.   if (synonly && tmp[0] == '#') return T;
  158.   errno = NIL;            /* zap error */
  159.                 /* validate name as directory */
  160.   return ((stat (mh_file (tmp,name),&sbuf) == 0) &&
  161.       (sbuf.st_mode & S_IFMT) == S_IFDIR);
  162. }
  163.  
  164.  
  165. /* MH manipulate driver parameters
  166.  * Accepts: function code
  167.  *        function-dependent value
  168.  * Returns: function-dependent return value
  169.  */
  170.  
  171. void *mh_parameters (long function,void *value)
  172. {
  173.   return NIL;
  174. }
  175.  
  176. /* MH mail find list of mailboxes
  177.  * Accepts: mail stream
  178.  *        pattern to search
  179.  */
  180.  
  181. void mh_find (MAILSTREAM *stream,char *pat)
  182. {
  183.   void *s = NIL;
  184.   char *t,tmp[MAILTMPLEN];
  185.                 /* read subscription database */
  186.   if (stream) while (t = sm_read (&s))
  187.     if (pmatch (t,pat) && mh_isvalid (t,tmp,T)) mm_mailbox (t);
  188. }
  189.  
  190.  
  191. /* MH mail find list of bboards
  192.  * Accepts: mail stream
  193.  *        pattern to search
  194.  */
  195.  
  196. void mh_find_bboards (MAILSTREAM *stream,char *pat)
  197. {
  198.   /* Always a no-op */
  199. }
  200.  
  201. /* MH mail find list of all mailboxes
  202.  * Accepts: mail stream
  203.  *        pattern to search
  204.  */
  205.  
  206. void mh_find_all (MAILSTREAM *stream,char *pat)
  207. {
  208.   DIR *dirp;
  209.   struct direct *d;
  210.   char tmp[MAILTMPLEN],file[MAILTMPLEN];
  211.   int i = 0;
  212.   char *s,*t;
  213.   if (!(pat[0] == '#' && (pat[1] == 'm' || pat[1] == 'M') &&
  214.     (pat[2] == 'h' || pat[2] == 'H') && pat[3] == '/')) return;
  215.                 /* must have a path */
  216.   if (!mh_path) mh_isvalid ("#MHINBOX",tmp,T);
  217.   if (!mh_path) return;        /* sorry */
  218.   memset (tmp,'\0',MAILTMPLEN);    /* init directory */
  219.   strcpy (tmp,mh_path);
  220.   memset (file,'\0',MAILTMPLEN);/* and mh mailbox name */
  221.   strcpy (file,"#mh/");
  222.                 /* directory specified in pattern, copy it */
  223.   if (s = strrchr (pat + 4,'/')) {
  224.     strncat (tmp,pat + 3,i = s - (pat + 3));
  225.     strncat (file,pat + 4,i);
  226.   }
  227.   i = strlen (file);        /* length of prefix */
  228.   if (dirp = opendir (tmp)) {    /* now open that directory */
  229.     while (d = readdir (dirp)) {/* for each directory entry */
  230.       strcpy (file + i,d->d_name);
  231.       if (((d->d_name[0] != '.') ||
  232.        (d->d_name[1] && ((d->d_name[1] != '.') || d->d_name[2]))) &&
  233.       pmatch (file,pat) && (mh_isvalid (file,tmp,NIL))) mm_mailbox (file);
  234.     }
  235.     closedir (dirp);        /* flush directory */
  236.   }
  237. }
  238.  
  239.  
  240. /* MH mail find list of all bboards
  241.  * Accepts: mail stream
  242.  *        pattern to search
  243.  */
  244.  
  245. void mh_find_all_bboards (MAILSTREAM *stream,char *pat)
  246. {
  247.   /* Always a no-op */
  248. }
  249.  
  250. /* MH mail subscribe to mailbox
  251.  * Accepts: mail stream
  252.  *        mailbox to add to subscription list
  253.  * Returns: T on success, NIL on failure
  254.  */
  255.  
  256. long mh_subscribe (MAILSTREAM *stream,char *mailbox)
  257. {
  258.   char tmp[MAILTMPLEN];
  259.   return sm_subscribe (mailbox);
  260. }
  261.  
  262.  
  263. /* MH mail unsubscribe to mailbox
  264.  * Accepts: mail stream
  265.  *        mailbox to delete from subscription list
  266.  * Returns: T on success, NIL on failure
  267.  */
  268.  
  269. long mh_unsubscribe (MAILSTREAM *stream,char *mailbox)
  270. {
  271.   char tmp[MAILTMPLEN];
  272.   return sm_unsubscribe (mailbox);
  273. }
  274.  
  275.  
  276. /* MH mail subscribe to bboard
  277.  * Accepts: mail stream
  278.  *        bboard to add to subscription list
  279.  * Returns: T on success, NIL on failure
  280.  */
  281.  
  282. long mh_subscribe_bboard (MAILSTREAM *stream,char *mailbox)
  283. {
  284.   return NIL;            /* never valid for MH */
  285. }
  286.  
  287.  
  288. /* MH mail unsubscribe to bboard
  289.  * Accepts: mail stream
  290.  *        bboard to delete from subscription list
  291.  * Returns: T on success, NIL on failure
  292.  */
  293.  
  294. long mh_unsubscribe_bboard (MAILSTREAM *stream,char *mailbox)
  295. {
  296.   return NIL;            /* never valid for MH */
  297. }
  298.  
  299. /* MH mail create mailbox
  300.  * Accepts: mail stream
  301.  *        mailbox name to create
  302.  * Returns: T on success, NIL on failure
  303.  */
  304.  
  305. long mh_create (MAILSTREAM *stream,char *mailbox)
  306. {
  307.   char tmp[MAILTMPLEN];
  308.   if (!(mailbox[0] == '#' && (mailbox[1] == 'm' || mailbox[1] == 'M') &&
  309.     (mailbox[2] == 'h' || mailbox[2] == 'H') && mailbox[3] == '/')) {
  310.     sprintf (tmp,"Can't create mailbox %s: invalid MH-format name",mailbox);
  311.     mm_log (tmp,ERROR);
  312.     return NIL;
  313.   }
  314.                 /* must not already exist */
  315.   if (mh_isvalid (mailbox,tmp,NIL)) {
  316.     sprintf (tmp,"Can't create mailbox %s: mailbox already exists",mailbox);
  317.     mm_log (tmp,ERROR);
  318.     return NIL;
  319.   }
  320.   if (!mh_path) return NIL;    /* sorry */
  321.   sprintf (tmp,"%s/%s",mh_path,mailbox + 4);
  322.   if (mkdir (tmp,0700)) {    /* try to make it */
  323.     sprintf (tmp,"Can't create mailbox %s: %s",mailbox,strerror (errno));
  324.     mm_log (tmp,ERROR);
  325.     return NIL;
  326.   }
  327.   return T;            /* return success */
  328. }
  329.  
  330. /* MH mail delete mailbox
  331.  *        mailbox name to delete
  332.  * Returns: T on success, NIL on failure
  333.  */
  334.  
  335. long mh_delete (MAILSTREAM *stream,char *mailbox)
  336. {
  337.   DIR *dirp;
  338.   struct direct *d;
  339.   int i;
  340.   char tmp[MAILTMPLEN];
  341.   if (!(mailbox[0] == '#' && (mailbox[1] == 'm' || mailbox[1] == 'M') &&
  342.     (mailbox[2] == 'h' || mailbox[2] == 'H') && mailbox[3] == '/')) {
  343.     sprintf (tmp,"Can't delete mailbox %s: invalid MH-format name",mailbox);
  344.     mm_log (tmp,ERROR);
  345.     return NIL;
  346.   }
  347.                 /* is mailbox valid? */
  348.   if (!mh_isvalid (mailbox,tmp,NIL)){
  349.     sprintf (tmp,"Can't delete mailbox %s: no such mailbox",mailbox);
  350.     mm_log (tmp,ERROR);
  351.     return NIL;
  352.   }
  353.                 /* get name of directory */
  354.   i = strlen (mh_file (tmp,mailbox));
  355.   if (dirp = opendir (tmp)) {    /* open directory */
  356.     tmp[i++] = '/';        /* now apply trailing delimiter */
  357.     while (d = readdir (dirp))    /* massacre all numeric or comma files */
  358.       if (mh_select (d) || *d->d_name == ',') {
  359.     strcpy (tmp + i,d->d_name);
  360.     unlink (tmp);        /* sayonara */
  361.       }
  362.     closedir (dirp);        /* flush directory */
  363.   }
  364.                 /* try to remove the directory */
  365.   if (rmdir (mh_file (tmp,mailbox))) {
  366.     sprintf (tmp,"Can't delete mailbox %s: %s",mailbox,strerror (errno));
  367.     mm_log (tmp,ERROR);
  368.     return NIL;
  369.   }
  370.   return T;            /* return success */
  371. }
  372.  
  373. /* MH mail rename mailbox
  374.  * Accepts: MH mail stream
  375.  *        old mailbox name
  376.  *        new mailbox name
  377.  * Returns: T on success, NIL on failure
  378.  */
  379.  
  380. long mh_rename (MAILSTREAM *stream,char *old,char *new)
  381. {
  382.   char tmp[MAILTMPLEN],tmp1[MAILTMPLEN];
  383.   if (!(old[0] == '#' && (old[1] == 'm' || old[1] == 'M') &&
  384.     (old[2] == 'h' || old[2] == 'H') && old[3] == '/')) {
  385.     sprintf (tmp,"Can't delete mailbox %s: invalid MH-format name",old);
  386.     mm_log (tmp,ERROR);
  387.     return NIL;
  388.   }
  389.                 /* old mailbox name must be valid */
  390.   if (!mh_isvalid (old,tmp,NIL)) {
  391.     sprintf (tmp,"Can't rename mailbox %s: no such mailbox",old);
  392.     mm_log (tmp,ERROR);
  393.     return NIL;
  394.   }
  395.   if (!(new[0] == '#' && (new[1] == 'm' || new[1] == 'M') &&
  396.     (new[2] == 'h' || new[2] == 'H') && new[3] == '/')) {
  397.     sprintf (tmp,"Can't rename to mailbox %s: invalid MH-format name",new);
  398.     mm_log (tmp,ERROR);
  399.     return NIL;
  400.   }
  401.   if (mh_isvalid (new,tmp,NIL)){/* new mailbox name must not be valid */
  402.     sprintf (tmp,"Can't rename to mailbox %s: destination already exists",new);
  403.     mm_log (tmp,ERROR);
  404.     return NIL;
  405.   }
  406.                 /* try to rename the directory */
  407.   if (rename (mh_file (tmp,old),mh_file (tmp1,new))) {
  408.     sprintf (tmp,"Can't rename mailbox %s to %s: %s",old,new,strerror (errno));
  409.     mm_log (tmp,ERROR);
  410.     return NIL;
  411.   }
  412.   return T;            /* return success */
  413. }
  414.  
  415. /* MH mail open
  416.  * Accepts: stream to open
  417.  * Returns: stream on success, NIL on failure
  418.  */
  419.  
  420. MAILSTREAM *mh_open (MAILSTREAM *stream)
  421. {
  422.   char tmp[MAILTMPLEN];
  423.   if (!stream) return &mhproto;    /* return prototype for OP_PROTOTYPE call */
  424.   if (LOCAL) {            /* close old file if stream being recycled */
  425.     mh_close (stream);        /* dump and save the changes */
  426.     stream->dtb = &mhdriver;    /* reattach this driver */
  427.     mail_free_cache (stream);    /* clean up cache */
  428.   }
  429.   stream->local = fs_get (sizeof (MHLOCAL));
  430.                 /* note if an INBOX or not */
  431.   LOCAL->inbox = !strcmp (ucase (strcpy (tmp,stream->mailbox)),"#MHINBOX");
  432.   mh_file (tmp,stream->mailbox);/* get directory name */
  433.   LOCAL->dir = cpystr (tmp);    /* copy directory name for later */
  434.   LOCAL->hdr = NIL;        /* no current header */
  435.                 /* make temporary buffer */
  436.   LOCAL->buf = (char *) fs_get ((LOCAL->buflen = MAXMESSAGESIZE) + 1);
  437.   LOCAL->scantime = 0;        /* not scanned yet */
  438.   stream->sequence++;        /* bump sequence number */
  439.                 /* parse mailbox */
  440.   stream->nmsgs = stream->recent = 0;
  441.   if (mh_ping (stream) && !(stream->nmsgs || stream->silent))
  442.     mm_log ("Mailbox is empty",(long) NIL);
  443.   return stream;        /* return stream to caller */
  444. }
  445.  
  446. /* MH mail close
  447.  * Accepts: MAIL stream
  448.  */
  449.  
  450. void mh_close (MAILSTREAM *stream)
  451. {
  452.   if (LOCAL) {            /* only if a file is open */
  453.     if (LOCAL->dir) fs_give ((void **) &LOCAL->dir);
  454.     mh_gc (stream,GC_TEXTS);    /* free local cache */
  455.                 /* free local scratch buffer */
  456.     if (LOCAL->buf) fs_give ((void **) &LOCAL->buf);
  457.                 /* nuke the local data */
  458.     fs_give ((void **) &stream->local);
  459.     stream->dtb = NIL;        /* log out the DTB */
  460.   }
  461. }
  462.  
  463.  
  464. /* MH mail fetch fast information
  465.  * Accepts: MAIL stream
  466.  *        sequence
  467.  */
  468.  
  469. void mh_fetchfast (MAILSTREAM *stream,char *sequence)
  470. {
  471.   long i;
  472.                 /* ugly and slow */
  473.   if (stream && LOCAL && mail_sequence (stream,sequence))
  474.     for (i = 1; i <= stream->nmsgs; i++)
  475.       if (mail_elt (stream,i)->sequence)
  476.     mh_fetchheader (stream,i);
  477. }
  478.  
  479.  
  480. /* MH mail fetch flags
  481.  * Accepts: MAIL stream
  482.  *        sequence
  483.  */
  484.  
  485. void mh_fetchflags (MAILSTREAM *stream,char *sequence)
  486. {
  487.   return;            /* no-op for local mail */
  488. }
  489.  
  490. /* MH mail fetch message structure
  491.  * Accepts: MAIL stream
  492.  *        message # to fetch
  493.  *        pointer to return body
  494.  * Returns: envelope of this message, body returned in body value
  495.  *
  496.  * Fetches the "fast" information as well
  497.  */
  498.  
  499. ENVELOPE *mh_fetchstructure (MAILSTREAM *stream,long msgno,BODY **body)
  500. {
  501.   char *h,*t;
  502.   LONGCACHE *lelt;
  503.   ENVELOPE **env;
  504.   STRING bs;
  505.   BODY **b;
  506.   if (stream->scache) {        /* short cache */
  507.     if (msgno != stream->msgno){/* flush old poop if a different message */
  508.       mail_free_envelope (&stream->env);
  509.       mail_free_body (&stream->body);
  510.     }
  511.     stream->msgno = msgno;
  512.     env = &stream->env;        /* get pointers to envelope and body */
  513.     b = &stream->body;
  514.   }
  515.   else {            /* long cache */
  516.     lelt = mail_lelt (stream,msgno);
  517.     env = &lelt->env;        /* get pointers to envelope and body */
  518.     b = &lelt->body;
  519.   }
  520.   if ((body && !*b) || !*env) {    /* have the poop we need? */
  521.     mail_free_envelope (env);    /* flush old envelope and body */
  522.     mail_free_body (b);
  523.     h = mh_fetchheader (stream,msgno);
  524.                 /* can't use fetchtext since it'll set seen */
  525.     t = stream->text ? stream->text : "";
  526.     INIT (&bs,mail_string,(void *) t,strlen (t));
  527.                 /* parse envelope and body */
  528.     rfc822_parse_msg (env,body ? b : NIL,h,strlen (h),&bs,mylocalhost (),
  529.               LOCAL->buf);
  530.   }
  531.   if (body) *body = *b;        /* return the body */
  532.   return *env;            /* return the envelope */
  533. }
  534.  
  535. /* MH mail fetch message header
  536.  * Accepts: MAIL stream
  537.  *        message # to fetch
  538.  * Returns: message header in RFC822 format
  539.  */
  540.  
  541. char *mh_fetchheader (MAILSTREAM *stream,long msgno)
  542. {
  543.   unsigned long i,hdrsize;
  544.   int fd;
  545.   char *s,*b,*t;
  546.   long m = msgno - 1;
  547.   struct stat sbuf;
  548.   struct tm *tm;
  549.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  550.                 /* build message file name */
  551.   sprintf (LOCAL->buf,"%s/%lu",LOCAL->dir,elt->data1);
  552.   if (stream->msgno != msgno) {
  553.     mh_gc (stream,GC_TEXTS);    /* invalidate current cache */
  554.     if ((fd = open (LOCAL->buf,O_RDONLY,NIL)) >= 0) {
  555.       fstat (fd,&sbuf);        /* get size of message */
  556.                 /* make plausible IMAPish date string */
  557.       tm = gmtime (&sbuf.st_mtime);
  558.       elt->day = tm->tm_mday; elt->month = tm->tm_mon + 1;
  559.       elt->year = tm->tm_year + 1900 - BASEYEAR;
  560.       elt->hours = tm->tm_hour; elt->minutes = tm->tm_min;
  561.       elt->seconds = tm->tm_sec;
  562.       elt->zhours = 0; elt->zminutes = 0;
  563.                 /* slurp message */
  564.       read (fd,s = (char *) fs_get (sbuf.st_size +1),sbuf.st_size);
  565.       s[sbuf.st_size] = '\0';    /* tie off file */
  566.       close (fd);        /* flush message file */
  567.       stream->msgno = msgno;    /* note current message number */
  568.                 /* find end of header */
  569.       for (i = 0,b = s; *b && !(i && (*b == '\n')); i = (*b++ == '\n'));
  570.       hdrsize = (*b ? ++b:b)-s; /* number of header bytes */
  571.       elt->rfc822_size =    /* size of entire message in CRLF form */
  572.     strcrlfcpy (&LOCAL->hdr,&i,s,hdrsize) +
  573.     strcrlfcpy (&stream->text,&i,b,sbuf.st_size - hdrsize);
  574.       fs_give ((void **) &s);    /* flush old data */
  575.     }
  576.   }
  577.   return LOCAL->hdr ? LOCAL->hdr : "";
  578. }
  579.  
  580. /* MH mail fetch message text (body only)
  581.  * Accepts: MAIL stream
  582.  *        message # to fetch
  583.  * Returns: message text in RFC822 format
  584.  */
  585.  
  586. char *mh_fetchtext (MAILSTREAM *stream,long msgno)
  587. {
  588.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  589.                 /* snarf message if don't have it yet */
  590.   if (stream->msgno != msgno) mh_fetchheader (stream,msgno);
  591.   if (!elt->seen) elt->seen = T;/* mark as seen */
  592.   return stream->text ? stream->text : "";
  593. }
  594.  
  595. /* MH fetch message body as a structure
  596.  * Accepts: Mail stream
  597.  *        message # to fetch
  598.  *        section specifier
  599.  *        pointer to length
  600.  * Returns: pointer to section of message body
  601.  */
  602.  
  603. char *mh_fetchbody (MAILSTREAM *stream,long m,char *s,unsigned long *len)
  604. {
  605.   BODY *b;
  606.   PART *pt;
  607.   unsigned long i;
  608.   char *base;
  609.   unsigned long offset = 0;
  610.   MESSAGECACHE *elt = mail_elt (stream,m);
  611.                 /* make sure have a body */
  612.   if (!(mh_fetchstructure (stream,m,&b) && b && s && *s &&
  613.     ((i = strtol (s,&s,10)) > 0) && (base = mh_fetchtext (stream,m))))
  614.     return NIL;
  615.   do {                /* until find desired body part */
  616.                 /* multipart content? */
  617.     if (b->type == TYPEMULTIPART) {
  618.       pt = b->contents.part;    /* yes, find desired part */
  619.       while (--i && (pt = pt->next));
  620.       if (!pt) return NIL;    /* bad specifier */
  621.                 /* note new body, check valid nesting */
  622.       if (((b = &pt->body)->type == TYPEMULTIPART) && !*s) return NIL;
  623.       offset = pt->offset;    /* get new offset */
  624.     }
  625.     else if (i != 1) return NIL;/* otherwise must be section 1 */
  626.                 /* need to go down further? */
  627.     if (i = *s) switch (b->type) {
  628.     case TYPEMESSAGE:        /* embedded message */
  629.       offset = b->contents.msg.offset;
  630.       b = b->contents.msg.body;    /* get its body, drop into multipart case */
  631.     case TYPEMULTIPART:        /* multipart, get next section */
  632.       if ((*s++ == '.') && (i = strtol (s,&s,10)) > 0) break;
  633.     default:            /* bogus subpart specification */
  634.       return NIL;
  635.     }
  636.   } while (i);
  637.                 /* lose if body bogus */
  638.   if ((!b) || b->type == TYPEMULTIPART) return NIL;
  639.   if (!elt->seen) elt->seen = T;/* mark as seen */
  640.   return rfc822_contents (&LOCAL->buf,&LOCAL->buflen,len,base + offset,
  641.               b->size.ibytes,b->encoding);
  642. }
  643.  
  644. /* MH mail set flag
  645.  * Accepts: MAIL stream
  646.  *        sequence
  647.  *        flag(s)
  648.  */
  649.  
  650. void mh_setflag (MAILSTREAM *stream,char *sequence,char *flag)
  651. {
  652.   MESSAGECACHE *elt;
  653.   long i;
  654.   short f = mh_getflags (stream,flag);
  655.   if (!f) return;        /* no-op if no flags to modify */
  656.                 /* get sequence and loop on it */
  657.   if (mail_sequence (stream,sequence)) for (i = 1; i <= stream->nmsgs; i++)
  658.     if ((elt = mail_elt (stream,i))->sequence) {
  659.                 /* set all requested flags */
  660.       if (f&fSEEN) elt->seen = T;
  661.       if (f&fDELETED) elt->deleted = T;
  662.       if (f&fFLAGGED) elt->flagged = T;
  663.       if (f&fANSWERED) elt->answered = T;
  664.     }
  665. }
  666.  
  667.  
  668. /* MH mail clear flag
  669.  * Accepts: MAIL stream
  670.  *        sequence
  671.  *        flag(s)
  672.  */
  673.  
  674. void mh_clearflag (MAILSTREAM *stream,char *sequence,char *flag)
  675. {
  676.   MESSAGECACHE *elt;
  677.   long i = stream->nmsgs;
  678.   short f = mh_getflags (stream,flag);
  679.   if (!f) return;        /* no-op if no flags to modify */
  680.                 /* get sequence and loop on it */
  681.   if (mail_sequence (stream,sequence)) for (i = 1; i <= stream->nmsgs; i++)
  682.     if ((elt = mail_elt (stream,i))->sequence) {
  683.                 /* clear all requested flags */
  684.       if (f&fSEEN) elt->seen = NIL;
  685.       if (f&fDELETED) elt->deleted = NIL;
  686.       if (f&fFLAGGED) elt->flagged = NIL;
  687.       if (f&fANSWERED) elt->answered = NIL;
  688.                 /* clearing either seen or deleted does this */
  689.     }
  690. }
  691.  
  692. /* MH mail search for messages
  693.  * Accepts: MAIL stream
  694.  *        search criteria
  695.  */
  696.  
  697. void mh_search (MAILSTREAM *stream,char *criteria)
  698. {
  699.   long i,n;
  700.   char *d;
  701.   search_t f;
  702.                 /* initially all searched */
  703.   for (i = 1; i <= stream->nmsgs; ++i) mail_elt (stream,i)->searched = T;
  704.                 /* get first criterion */
  705.   if (criteria && (criteria = strtok (criteria," "))) {
  706.                 /* for each criterion */
  707.     for (; criteria; (criteria = strtok (NIL," "))) {
  708.       f = NIL; d = NIL; n = 0;    /* init then scan the criterion */
  709.       switch (*ucase (criteria)) {
  710.       case 'A':            /* possible ALL, ANSWERED */
  711.     if (!strcmp (criteria+1,"LL")) f = mh_search_all;
  712.     else if (!strcmp (criteria+1,"NSWERED")) f = mh_search_answered;
  713.     break;
  714.       case 'B':            /* possible BCC, BEFORE, BODY */
  715.     if (!strcmp (criteria+1,"CC"))
  716.       f = mh_search_string (mh_search_bcc,&d,&n);
  717.     else if (!strcmp (criteria+1,"EFORE"))
  718.       f = mh_search_date (mh_search_before,&n);
  719.     else if (!strcmp (criteria+1,"ODY"))
  720.       f = mh_search_string (mh_search_body,&d,&n);
  721.     break;
  722.       case 'C':            /* possible CC */
  723.     if (!strcmp (criteria+1,"C")) 
  724.       f = mh_search_string (mh_search_cc,&d,&n);
  725.     break;
  726.       case 'D':            /* possible DELETED */
  727.     if (!strcmp (criteria+1,"ELETED")) f = mh_search_deleted;
  728.     break;
  729.       case 'F':            /* possible FLAGGED, FROM */
  730.     if (!strcmp (criteria+1,"LAGGED")) f = mh_search_flagged;
  731.     else if (!strcmp (criteria+1,"ROM"))
  732.       f = mh_search_string (mh_search_from,&d,&n);
  733.     break;
  734.       case 'K':            /* possible KEYWORD */
  735.     if (!strcmp (criteria+1,"EYWORD"))
  736.       f = mh_search_flag (mh_search_keyword,&d);
  737.     break;
  738.       case 'N':            /* possible NEW */
  739.     if (!strcmp (criteria+1,"EW")) f = mh_search_new;
  740.     break;
  741.  
  742.       case 'O':            /* possible OLD, ON */
  743.     if (!strcmp (criteria+1,"LD")) f = mh_search_old;
  744.     else if (!strcmp (criteria+1,"N"))
  745.       f = mh_search_date (mh_search_on,&n);
  746.     break;
  747.       case 'R':            /* possible RECENT */
  748.     if (!strcmp (criteria+1,"ECENT")) f = mh_search_recent;
  749.     break;
  750.       case 'S':            /* possible SEEN, SINCE, SUBJECT */
  751.     if (!strcmp (criteria+1,"EEN")) f = mh_search_seen;
  752.     else if (!strcmp (criteria+1,"INCE"))
  753.       f = mh_search_date (mh_search_since,&n);
  754.     else if (!strcmp (criteria+1,"UBJECT"))
  755.       f = mh_search_string (mh_search_subject,&d,&n);
  756.     break;
  757.       case 'T':            /* possible TEXT, TO */
  758.     if (!strcmp (criteria+1,"EXT"))
  759.       f = mh_search_string (mh_search_text,&d,&n);
  760.     else if (!strcmp (criteria+1,"O"))
  761.       f = mh_search_string (mh_search_to,&d,&n);
  762.     break;
  763.       case 'U':            /* possible UN* */
  764.     if (criteria[1] == 'N') {
  765.       if (!strcmp (criteria+2,"ANSWERED")) f = mh_search_unanswered;
  766.       else if (!strcmp (criteria+2,"DELETED")) f = mh_search_undeleted;
  767.       else if (!strcmp (criteria+2,"FLAGGED")) f = mh_search_unflagged;
  768.       else if (!strcmp (criteria+2,"KEYWORD"))
  769.         f = mh_search_flag (mh_search_unkeyword,&d);
  770.       else if (!strcmp (criteria+2,"SEEN")) f = mh_search_unseen;
  771.     }
  772.     break;
  773.       default:            /* we will barf below */
  774.     break;
  775.       }
  776.       if (!f) {            /* if can't determine any criteria */
  777.     sprintf (LOCAL->buf,"Unknown search criterion: %s",criteria);
  778.     mm_log (LOCAL->buf,ERROR);
  779.     return;
  780.       }
  781.                 /* run the search criterion */
  782.       for (i = 1; i <= stream->nmsgs; ++i)
  783.     if (mail_elt (stream,i)->searched && !(*f) (stream,i,d,n))
  784.       mail_elt (stream,i)->searched = NIL;
  785.     }
  786.                 /* report search results to main program */
  787.     for (i = 1; i <= stream->nmsgs; ++i)
  788.       if (mail_elt (stream,i)->searched) mail_searched (stream,i);
  789.   }
  790. }
  791.  
  792. /* MH mail ping mailbox
  793.  * Accepts: MAIL stream
  794.  * Returns: T if stream alive, else NIL
  795.  */
  796.  
  797. long mh_ping (MAILSTREAM *stream)
  798. {
  799.   MAILSTREAM *sysibx = NIL;
  800.   MESSAGECACHE *elt,*selt;
  801.   struct stat sbuf;
  802.   char *s,tmp[MAILTMPLEN];
  803.   int fd;
  804.   long i,j,r,old;
  805.   long nmsgs = stream->nmsgs;
  806.   long recent = stream->recent;
  807.   stat (LOCAL->dir,&sbuf);
  808.   if (sbuf.st_ctime != LOCAL->scantime) {
  809.     struct direct **names = NIL;
  810.     long nfiles = scandir (LOCAL->dir,&names,mh_select,mh_numsort);
  811.     old = nmsgs ? mail_elt (stream,nmsgs)->data1 : 0;
  812.                 /* note scanned now */
  813.     LOCAL->scantime = sbuf.st_ctime;
  814.                 /* scan directory */
  815.     for (i = 0; i < nfiles; ++i) {
  816.                 /* if newly seen, add to list */
  817.       if ((j = atoi (names[i]->d_name)) > old) {
  818.     (elt = mail_elt (stream,++nmsgs))->data1 = j;
  819.     elt->valid = T;        /* note valid flags */
  820.     if (old) {        /* other than the first pass? */
  821.       elt->recent = T;    /* yup, mark as recent */
  822.       recent++;        /* bump recent count */
  823.     }
  824.     else {            /* see if already read */
  825.       sprintf (tmp,"%s/%s",LOCAL->dir,names[i]->d_name);
  826.       stat (tmp,&sbuf);    /* get inode poop */
  827.       if (sbuf.st_atime > sbuf.st_mtime) elt->seen = T;
  828.     }
  829.       }
  830.       fs_give ((void **) &names[i]);
  831.     }
  832.                 /* free directory */
  833.     if (names) fs_give ((void **) &names);
  834.   }
  835.  
  836.   if (LOCAL->inbox) {        /* if INBOX, snarf from system INBOX  */
  837.     old = nmsgs ? mail_elt (stream,nmsgs)->data1 : 0;
  838.                 /* paranoia check */
  839.     if (!strcmp (sysinbox (),stream->mailbox)) return NIL;
  840.     mm_critical (stream);    /* go critical */
  841.     stat (sysinbox (),&sbuf);    /* see if anything there */
  842.                 /* can get sysinbox mailbox? */
  843.     if (sbuf.st_size && (sysibx = mail_open (sysibx,sysinbox (),OP_SILENT))
  844.     && (!sysibx->rdonly) && (r = sysibx->nmsgs)) {
  845.       for (i = 1; i <= r; ++i) {/* for each message in sysinbox mailbox */
  846.                 /* build file name we will use */
  847.     sprintf (LOCAL->buf,"%s/%lu",LOCAL->dir,++old);
  848.                 /* snarf message from Berkeley mailbox */
  849.     s = bezerk_snarf (sysibx,i,&j);
  850.     selt = mail_elt (sysibx,i);
  851.     if (((fd = open (LOCAL->buf,O_WRONLY|O_CREAT|O_EXCL,
  852.              S_IREAD|S_IWRITE)) >= 0) &&
  853.         (write (fd,s,j) == j) && (!fsync (fd)) && (!close (fd))) {
  854.                 /* create new elt, note its file number */
  855.       (elt = mail_elt (stream,++nmsgs))->data1 = old;
  856.       recent++;        /* bump recent count */
  857.                 /* set up initial flags and date */
  858.       elt->valid = elt->recent = T;
  859.       elt->seen = selt->seen;
  860.       elt->deleted = selt->deleted;
  861.       elt->flagged = selt->flagged;
  862.       elt->answered = selt->answered;
  863.       elt->day = selt->day;elt->month = selt->month;elt->year = selt->year;
  864.       elt->hours = selt->hours;elt->minutes = selt->minutes;
  865.       elt->seconds = selt->seconds;
  866.       elt->zhours = selt->zhours; elt->zminutes = selt->zminutes;
  867.       /* should set the file date too */
  868.     }
  869.     else {            /* failed to snarf */
  870.       if (fd) {        /* did it ever get opened? */
  871.         close (fd);        /* close descriptor */
  872.         unlink (LOCAL->buf);/* flush this file */
  873.       }
  874.       return NIL;        /* note that something is badly wrong */
  875.     }
  876.     selt->deleted = T;    /* delete it from the sysinbox */
  877.       }
  878.       stat (LOCAL->dir,&sbuf);    /* update scan time */
  879.       LOCAL->scantime = sbuf.st_ctime;      
  880.       mail_expunge (sysibx);    /* now expunge all those messages */
  881.     }
  882.     if (sysibx) mail_close (sysibx);
  883.     mm_nocritical (stream);    /* release critical */
  884.   }
  885.   mail_exists (stream,nmsgs);    /* notify upper level of mailbox size */
  886.   mail_recent (stream,recent);
  887.   return T;            /* return that we are alive */
  888. }
  889.  
  890. /* MH mail check mailbox
  891.  * Accepts: MAIL stream
  892.  */
  893.  
  894. void mh_check (MAILSTREAM *stream)
  895. {
  896.   /* Perhaps in the future this will preserve flags */
  897.   if (mh_ping (stream)) mm_log ("Check completed",(long) NIL);
  898. }
  899.  
  900.  
  901. /* MH mail expunge mailbox
  902.  * Accepts: MAIL stream
  903.  */
  904.  
  905. void mh_expunge (MAILSTREAM *stream)
  906. {
  907.   MESSAGECACHE *elt;
  908.   unsigned long j;
  909.   unsigned long i = 1;
  910.   unsigned long n = 0;
  911.   unsigned long recent = stream->recent;
  912.   mh_gc (stream,GC_TEXTS);    /* invalidate texts */
  913.   mm_critical (stream);        /* go critical */
  914.   while (i <= stream->nmsgs) {    /* for each message */
  915.                 /* if deleted, need to trash it */
  916.     if ((elt = mail_elt (stream,i))->deleted) {
  917.       sprintf (LOCAL->buf,"%s/%lu",LOCAL->dir,elt->data1);
  918.       if (unlink (LOCAL->buf)) {/* try to delete the message */
  919.     sprintf (LOCAL->buf,"Expunge of message %ld failed, aborted: %s",i,
  920.          strerror (errno));
  921.     mm_log (LOCAL->buf,(long) NIL);
  922.     break;
  923.       }
  924.       if (elt->recent) --recent;/* if recent, note one less recent message */
  925.       mail_expunged (stream,i);    /* notify upper levels */
  926.       n++;            /* count up one more expunged message */
  927.     }
  928.     else i++;            /* otherwise try next message */
  929.   }
  930.   if (n) {            /* output the news if any expunged */
  931.     sprintf (LOCAL->buf,"Expunged %ld messages",n);
  932.     mm_log (LOCAL->buf,(long) NIL);
  933.   }
  934.   else mm_log ("No messages deleted, so no update needed",(long) NIL);
  935.   mm_nocritical (stream);    /* release critical */
  936.                 /* notify upper level of new mailbox size */
  937.   mail_exists (stream,stream->nmsgs);
  938.   mail_recent (stream,recent);
  939. }
  940.  
  941. /* MH mail copy message(s)
  942.  * Accepts: MAIL stream
  943.  *        sequence
  944.  *        destination mailbox
  945.  * Returns: T if copy successful, else NIL
  946.  */
  947.  
  948. long mh_copy (MAILSTREAM *stream,char *sequence,char *mailbox)
  949. {
  950.   STRING st;
  951.   MESSAGECACHE *elt;
  952.   struct stat sbuf;
  953.   int fd;
  954.   long i;
  955.   char *s,tmp[MAILTMPLEN];
  956.                 /* copy the messages */
  957.   if (mail_sequence (stream,sequence)) for (i = 1; i <= stream->nmsgs; i++)
  958.     if ((elt = mail_elt (stream,i))->sequence) {
  959.       sprintf (LOCAL->buf,"%s/%lu",LOCAL->dir,elt->data1);
  960.       if ((fd = open (LOCAL->buf,O_RDONLY,NIL)) < 0) return NIL;
  961.       fstat (fd,&sbuf);        /* get size of message */
  962.                 /* slurp message */
  963.       read (fd,s = (char *) fs_get (sbuf.st_size +1),sbuf.st_size);
  964.       s[sbuf.st_size] = '\0';    /* tie off file */
  965.       close (fd);        /* flush message file */
  966.       INIT (&st,mail_string,(void *) s,sbuf.st_size);
  967.       sprintf (LOCAL->buf,"%s%s%s%s%s)",
  968.            elt->seen ? " \\Seen" : "",
  969.            elt->deleted ? " \\Deleted" : "",
  970.            elt->flagged ? " \\Flagged" : "",
  971.            elt->answered ? " \\Answered" : "",
  972.            (elt->seen || elt->deleted || elt->flagged || elt->answered) ?
  973.            "" : " ");
  974.       LOCAL->buf[0] = '(';    /* open list */
  975.       mail_date (tmp,elt);    /* generate internal date */
  976.       if (!mh_append (stream,mailbox,LOCAL->buf,tmp,&st)) {
  977.     fs_give ((void **) &s);    /* give back temporary space */
  978.     return NIL;
  979.       }
  980.       fs_give ((void **) &s);    /* give back temporary space */
  981.     }
  982.   return T;            /* return success */
  983. }
  984.  
  985. /* MH mail move message(s)
  986.  * Accepts: MAIL stream
  987.  *        sequence
  988.  *        destination mailbox
  989.  * Returns: T if move successful, else NIL
  990.  */
  991.  
  992. long mh_move (MAILSTREAM *stream,char *sequence,char *mailbox)
  993. {
  994.   MESSAGECACHE *elt;
  995.   if (mh_copy (stream,sequence,mailbox)) return NIL;
  996.                 /* delete all requested messages */
  997.   mh_setflag (stream,sequence,"\\Deleted");
  998.   return T;
  999. }
  1000.  
  1001. /* MH mail append message from stringstruct
  1002.  * Accepts: MAIL stream
  1003.  *        destination mailbox
  1004.  *        stringstruct of messages to append
  1005.  * Returns: T if append successful, else NIL
  1006.  */
  1007.  
  1008. long mh_append (MAILSTREAM *stream,char *mailbox,char *flags,char *date,
  1009.         STRING *message)
  1010. {
  1011.   struct stat sbuf;
  1012.   struct direct **names;
  1013.   int fd;
  1014.   char c,*s,*t,tmp[MAILTMPLEN];
  1015.   MESSAGECACHE elt;
  1016.   long i,last,nfiles;
  1017.   long size = 0;
  1018.   long ret = LONGT;
  1019.   short f = 0;
  1020.   if (flags) {            /* get flags if given */
  1021.     MAILSTREAM *st = mail_open (NIL,mailbox,OP_SILENT);
  1022.     long ruf;
  1023.     f = mh_getflags (st,flags);
  1024.     mail_close (st);
  1025.   }
  1026.   if (date) {            /* want to preserve date? */
  1027.                 /* yes, parse date into an elt */
  1028.     if (!mail_parse_date (&elt,date)) {
  1029.       sprintf (tmp,"Bad date in append: %s",date);
  1030.       mm_log (tmp,ERROR);
  1031.       return NIL;
  1032.     }
  1033.   }
  1034.                 /* N.B.: can't use LOCAL->buf for tmp */
  1035.                 /* make sure valid mailbox */
  1036.   if (!mh_isvalid (mailbox,tmp,NIL)) switch (errno) {
  1037.   case ENOENT:            /* no such file? */
  1038.     mm_notify (stream,"[TRYCREATE] Must create mailbox before append",NIL);
  1039.     return NIL;
  1040.   case 0:            /* merely empty file? */
  1041.     break;
  1042.   case EINVAL:
  1043.     sprintf (tmp,"Invalid MH-format mailbox name: %s",mailbox);
  1044.     mm_log (tmp,ERROR);
  1045.     return NIL;
  1046.   default:
  1047.     sprintf (tmp,"Not a MH-format mailbox: %s",mailbox);
  1048.     mm_log (tmp,ERROR);
  1049.     return NIL;
  1050.   }
  1051.   mh_file (tmp,mailbox);    /* build file name we will use */
  1052.   if (nfiles = scandir (tmp,&names,mh_select,mh_numsort)) {
  1053.                 /* largest number */
  1054.     last = atoi (names[nfiles-1]->d_name);    
  1055.     for (i = 0; i < nfiles; ++i) /* free directory */
  1056.       fs_give ((void **) &names[i]);
  1057.   }
  1058.   else last = 0;        /* no messages here yet */
  1059.   if (names) fs_give ((void **) &names);
  1060.  
  1061.   sprintf (tmp + strlen (tmp),"/%lu",++last);
  1062.   if ((fd = open (tmp,O_WRONLY|O_CREAT|O_EXCL,S_IREAD|S_IWRITE)) < 0) {
  1063.     sprintf (tmp,"Can't open append mailbox: %s",strerror (errno));
  1064.     mm_log (tmp,ERROR);
  1065.     return NIL;
  1066.   }
  1067.   i = SIZE (message);        /* get size of message */
  1068.   s = (char *) fs_get (i + 1);    /* get space for the data */
  1069.                 /* copy the data w/o CR's */
  1070.   while (i--) if ((c = SNX (message)) != '\015') s[size++] = c;
  1071.   mm_critical (stream);        /* go critical */
  1072.                 /* write the data */
  1073.   if ((write (fd,s,size) < 0) || fsync (fd)) {
  1074.     unlink (tmp);        /* delete mailbox */
  1075.     sprintf (tmp,"Message append failed: %s",strerror (errno));
  1076.     mm_log (tmp,ERROR);
  1077.     ret = NIL;
  1078.   }
  1079.   close (fd);            /* close the file */
  1080.   mm_nocritical (stream);    /* release critical */
  1081.   fs_give ((void **) &s);    /* flush the buffer */
  1082.   return ret;
  1083. }
  1084.  
  1085. /* MH garbage collect stream
  1086.  * Accepts: Mail stream
  1087.  *        garbage collection flags
  1088.  */
  1089.  
  1090. void mh_gc (MAILSTREAM *stream,long gcflags)
  1091. {
  1092.   unsigned long i;
  1093.   if (gcflags & GC_TEXTS) {    /* garbage collect texts? */
  1094.                 /* flush texts from cache */
  1095.     if (LOCAL->hdr) fs_give ((void **) &LOCAL->hdr);
  1096.     if (stream->text) fs_give ((void **) &stream->text);
  1097.     stream->msgno = 0;        /* invalidate stream text */
  1098.   }
  1099. }
  1100.  
  1101. /* Internal routines */
  1102.  
  1103.  
  1104. /* MH file name selection test
  1105.  * Accepts: candidate directory entry
  1106.  * Returns: T to use file name, NIL to skip it
  1107.  */
  1108.  
  1109. int mh_select (struct direct *name)
  1110. {
  1111.   char c;
  1112.   char *s = name->d_name;
  1113.   while (c = *s++) if (!isdigit (c)) return NIL;
  1114.   return T;
  1115. }
  1116.  
  1117.  
  1118. /* MH file name comparision
  1119.  * Accepts: first candidate directory entry
  1120.  *        second candidate directory entry
  1121.  * Returns: negative if d1 < d2, 0 if d1 == d2, postive if d1 > d2
  1122.  */
  1123.  
  1124. int mh_numsort (struct direct **d1,struct direct **d2)
  1125. {
  1126.   return (atoi ((*d1)->d_name) - atoi ((*d2)->d_name));
  1127. }
  1128.  
  1129.  
  1130. /* MH mail build file name
  1131.  * Accepts: destination string
  1132.  *          source
  1133.  * Returns: destination
  1134.  */
  1135.  
  1136. char *mh_file (char *dst,char *name)
  1137. {
  1138.   char tmp[MAILTMPLEN];
  1139.                 /* build composite name */
  1140.   sprintf (dst,"%s/%s",mh_path,strcmp (ucase (strcpy (tmp,name)),"#MHINBOX") ?
  1141.        name + 4 : "inbox");
  1142.   return dst;
  1143. }
  1144.  
  1145. /* Parse flag list
  1146.  * Accepts: MAIL stream
  1147.  *        flag list as a character string
  1148.  * Returns: flag command list
  1149.  */
  1150.  
  1151. short mh_getflags (MAILSTREAM *stream,char *flag)
  1152. {
  1153.   char *t,tmp[MAILTMPLEN],err[MAILTMPLEN];
  1154.   short f = 0;
  1155.   short i,j;
  1156.   if (flag && *flag) {        /* no-op if no flag string */
  1157.                 /* check if a list and make sure valid */
  1158.     if ((i = (*flag == '(')) ^ (flag[strlen (flag)-1] == ')')) {
  1159.       mm_log ("Bad flag list",ERROR);
  1160.       return NIL;
  1161.     }
  1162.                 /* copy the flag string w/o list construct */
  1163.     strncpy (tmp,flag+i,(j = strlen (flag) - (2*i)));
  1164.     tmp[j] = '\0';
  1165.     t = ucase (tmp);        /* uppercase only from now on */
  1166.  
  1167.     while (t && *t) {        /* parse the flags */
  1168.       if (*t == '\\') {        /* system flag? */
  1169.     switch (*++t) {        /* dispatch based on first character */
  1170.     case 'S':        /* possible \Seen flag */
  1171.       if (t[1] == 'E' && t[2] == 'E' && t[3] == 'N') i = fSEEN;
  1172.       t += 4;        /* skip past flag name */
  1173.       break;
  1174.     case 'D':        /* possible \Deleted flag */
  1175.       if (t[1] == 'E' && t[2] == 'L' && t[3] == 'E' && t[4] == 'T' &&
  1176.           t[5] == 'E' && t[6] == 'D') i = fDELETED;
  1177.       t += 7;        /* skip past flag name */
  1178.       break;
  1179.     case 'F':        /* possible \Flagged flag */
  1180.       if (t[1] == 'L' && t[2] == 'A' && t[3] == 'G' && t[4] == 'G' &&
  1181.           t[5] == 'E' && t[6] == 'D') i = fFLAGGED;
  1182.       t += 7;        /* skip past flag name */
  1183.       break;
  1184.     case 'A':        /* possible \Answered flag */
  1185.       if (t[1] == 'N' && t[2] == 'S' && t[3] == 'W' && t[4] == 'E' &&
  1186.           t[5] == 'R' && t[6] == 'E' && t[7] == 'D') i = fANSWERED;
  1187.       t += 8;        /* skip past flag name */
  1188.       break;
  1189.     default:        /* unknown */
  1190.       i = 0;
  1191.       break;
  1192.     }
  1193.                 /* add flag to flags list */
  1194.     if (i && ((*t == '\0') || (*t++ == ' '))) f |= i;
  1195.       }
  1196.       else {            /* no user flags yet */
  1197.     t = strtok (t," ");    /* isolate flag name */
  1198.     sprintf (err,"Unknown flag: %.80s",t);
  1199.     t = strtok (NIL," ");    /* get next flag */
  1200.     mm_log (err,ERROR);
  1201.       }
  1202.     }
  1203.   }
  1204.   return f;
  1205. }
  1206.  
  1207. /* Search support routines
  1208.  * Accepts: MAIL stream
  1209.  *        message number
  1210.  *        pointer to additional data
  1211.  *        pointer to temporary buffer
  1212.  * Returns: T if search matches, else NIL
  1213.  */
  1214.  
  1215. char mh_search_all (MAILSTREAM *stream,long msgno,char *d,long n)
  1216. {
  1217.   return T;            /* ALL always succeeds */
  1218. }
  1219.  
  1220.  
  1221. char mh_search_answered (MAILSTREAM *stream,long msgno,char *d,long n)
  1222. {
  1223.   return mail_elt (stream,msgno)->answered ? T : NIL;
  1224. }
  1225.  
  1226.  
  1227. char mh_search_deleted (MAILSTREAM *stream,long msgno,char *d,long n)
  1228. {
  1229.   return mail_elt (stream,msgno)->deleted ? T : NIL;
  1230. }
  1231.  
  1232.  
  1233. char mh_search_flagged (MAILSTREAM *stream,long msgno,char *d,long n)
  1234. {
  1235.   return mail_elt (stream,msgno)->flagged ? T : NIL;
  1236. }
  1237.  
  1238.  
  1239. char mh_search_keyword (MAILSTREAM *stream,long msgno,char *d,long n)
  1240. {
  1241.   return NIL;            /* keywords not supported yet */
  1242. }
  1243.  
  1244.  
  1245. char mh_search_new (MAILSTREAM *stream,long msgno,char *d,long n)
  1246. {
  1247.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  1248.   return (elt->recent && !elt->seen) ? T : NIL;
  1249. }
  1250.  
  1251. char mh_search_old (MAILSTREAM *stream,long msgno,char *d,long n)
  1252. {
  1253.   return mail_elt (stream,msgno)->recent ? NIL : T;
  1254. }
  1255.  
  1256.  
  1257. char mh_search_recent (MAILSTREAM *stream,long msgno,char *d,long n)
  1258. {
  1259.   return mail_elt (stream,msgno)->recent ? T : NIL;
  1260. }
  1261.  
  1262.  
  1263. char mh_search_seen (MAILSTREAM *stream,long msgno,char *d,long n)
  1264. {
  1265.   return mail_elt (stream,msgno)->seen ? T : NIL;
  1266. }
  1267.  
  1268.  
  1269. char mh_search_unanswered (MAILSTREAM *stream,long msgno,char *d,long n)
  1270. {
  1271.   return mail_elt (stream,msgno)->answered ? NIL : T;
  1272. }
  1273.  
  1274.  
  1275. char mh_search_undeleted (MAILSTREAM *stream,long msgno,char *d,long n)
  1276. {
  1277.   return mail_elt (stream,msgno)->deleted ? NIL : T;
  1278. }
  1279.  
  1280.  
  1281. char mh_search_unflagged (MAILSTREAM *stream,long msgno,char *d,long n)
  1282. {
  1283.   return mail_elt (stream,msgno)->flagged ? NIL : T;
  1284. }
  1285.  
  1286.  
  1287. char mh_search_unkeyword (MAILSTREAM *stream,long msgno,char *d,long n)
  1288. {
  1289.   return T;            /* keywords not supported yet */
  1290. }
  1291.  
  1292.  
  1293. char mh_search_unseen (MAILSTREAM *stream,long msgno,char *d,long n)
  1294. {
  1295.   return mail_elt (stream,msgno)->seen ? NIL : T;
  1296. }
  1297.  
  1298. char mh_search_before (MAILSTREAM *stream,long msgno,char *d,long n)
  1299. {
  1300.   return (char) (mh_msgdate (stream,msgno) < n);
  1301. }
  1302.  
  1303.  
  1304. char mh_search_on (MAILSTREAM *stream,long msgno,char *d,long n)
  1305. {
  1306.   return (char) (mh_msgdate (stream,msgno) == n);
  1307. }
  1308.  
  1309.  
  1310. char mh_search_since (MAILSTREAM *stream,long msgno,char *d,long n)
  1311. {
  1312.                 /* everybody interprets "since" as .GE. */
  1313.   return (char) (mh_msgdate (stream,msgno) >= n);
  1314. }
  1315.  
  1316.  
  1317. unsigned long mh_msgdate (MAILSTREAM *stream,long msgno)
  1318. {
  1319.   struct stat sbuf;
  1320.   struct tm *tm;
  1321.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  1322.   if (!elt->day) {        /* get date if don't have it yet */
  1323.                 /* build message file name */
  1324.     sprintf (LOCAL->buf,"%s/%lu",LOCAL->dir,elt->data1);
  1325.     stat (LOCAL->buf,&sbuf);    /* get message date */
  1326.     tm = gmtime (&sbuf.st_mtime);
  1327.     elt->day = tm->tm_mday; elt->month = tm->tm_mon + 1;
  1328.     elt->year = tm->tm_year + 1900 - BASEYEAR;
  1329.     elt->hours = tm->tm_hour; elt->minutes = tm->tm_min;
  1330.     elt->seconds = tm->tm_sec;
  1331.     elt->zhours = 0; elt->zminutes = 0;
  1332.   }
  1333.   return (long) (elt->year << 9) + (elt->month << 5) + elt->day;
  1334. }
  1335.  
  1336. char mh_search_body (MAILSTREAM *stream,long msgno,char *d,long n)
  1337. {
  1338.   mh_fetchheader (stream,msgno);
  1339.   return stream->text ? search (stream->text,strlen (stream->text),d,n) : NIL;
  1340. }
  1341.  
  1342.  
  1343. char mh_search_subject (MAILSTREAM *stream,long msgno,char *d,long n)
  1344. {
  1345.   char *t = mh_fetchstructure (stream,msgno,NIL)->subject;
  1346.   return t ? search (t,strlen (t),d,n) : NIL;
  1347. }
  1348.  
  1349.  
  1350. char mh_search_text (MAILSTREAM *stream,long msgno,char *d,long n)
  1351. {
  1352.   char *t = mh_fetchheader (stream,msgno);
  1353.   return (t && search (t,strlen (t),d,n)) ||
  1354.     mh_search_body (stream,msgno,d,n);
  1355. }
  1356.  
  1357. char mh_search_bcc (MAILSTREAM *stream,long msgno,char *d,long n)
  1358. {
  1359.   ADDRESS *a = mh_fetchstructure (stream,msgno,NIL)->bcc;
  1360.   LOCAL->buf[0] = '\0';        /* initially empty string */
  1361.                 /* get text for address */
  1362.   rfc822_write_address (LOCAL->buf,a);
  1363.   return search (LOCAL->buf,(long) strlen (LOCAL->buf),d,n);
  1364. }
  1365.  
  1366.  
  1367. char mh_search_cc (MAILSTREAM *stream,long msgno,char *d,long n)
  1368. {
  1369.   ADDRESS *a = mh_fetchstructure (stream,msgno,NIL)->cc;
  1370.   LOCAL->buf[0] = '\0';        /* initially empty string */
  1371.                 /* get text for address */
  1372.   rfc822_write_address (LOCAL->buf,a);
  1373.   return search (LOCAL->buf,(long) strlen (LOCAL->buf),d,n);
  1374. }
  1375.  
  1376.  
  1377. char mh_search_from (MAILSTREAM *stream,long msgno,char *d,long n)
  1378. {
  1379.   ADDRESS *a = mh_fetchstructure (stream,msgno,NIL)->from;
  1380.   LOCAL->buf[0] = '\0';        /* initially empty string */
  1381.                 /* get text for address */
  1382.   rfc822_write_address (LOCAL->buf,a);
  1383.   return search (LOCAL->buf,(long) strlen (LOCAL->buf),d,n);
  1384. }
  1385.  
  1386.  
  1387. char mh_search_to (MAILSTREAM *stream,long msgno,char *d,long n)
  1388. {
  1389.   ADDRESS *a = mh_fetchstructure (stream,msgno,NIL)->to;
  1390.   LOCAL->buf[0] = '\0';        /* initially empty string */
  1391.                 /* get text for address */
  1392.   rfc822_write_address (LOCAL->buf,a);
  1393.   return search (LOCAL->buf,(long) strlen (LOCAL->buf),d,n);
  1394. }
  1395.  
  1396. /* Search parsers */
  1397.  
  1398.  
  1399. /* Parse a date
  1400.  * Accepts: function to return
  1401.  *        pointer to date integer to return
  1402.  * Returns: function to return
  1403.  */
  1404.  
  1405. search_t mh_search_date (search_t f,long *n)
  1406. {
  1407.   long i;
  1408.   char *s;
  1409.   MESSAGECACHE elt;
  1410.                 /* parse the date and return fn if OK */
  1411.   return (mh_search_string (f,&s,&i) && mail_parse_date (&elt,s) &&
  1412.       (*n = (elt.year << 9) + (elt.month << 5) + elt.day)) ? f : NIL;
  1413. }
  1414.  
  1415. /* Parse a flag
  1416.  * Accepts: function to return
  1417.  *        pointer to string to return
  1418.  * Returns: function to return
  1419.  */
  1420.  
  1421. search_t mh_search_flag (search_t f,char **d)
  1422. {
  1423.                 /* get a keyword, return if OK */
  1424.   return (*d = strtok (NIL," ")) ? f : NIL;
  1425. }
  1426.  
  1427.  
  1428. /* Parse a string
  1429.  * Accepts: function to return
  1430.  *        pointer to string to return
  1431.  *        pointer to string length to return
  1432.  * Returns: function to return
  1433.  */
  1434.  
  1435. search_t mh_search_string (search_t f,char **d,long *n)
  1436. {
  1437.   char *end = " ";
  1438.   char *c = strtok (NIL,"");    /* remainder of criteria */
  1439.   if (!c) return NIL;        /* missing argument */
  1440.   switch (*c) {            /* see what the argument is */
  1441.   case '{':            /* literal string */
  1442.     *n = strtol (c+1,d,10);    /* get its length */
  1443.     if ((*(*d)++ == '}') && (*(*d)++ == '\015') && (*(*d)++ == '\012') &&
  1444.     (!(*(c = *d + *n)) || (*c == ' '))) {
  1445.       char e = *--c;
  1446.       *c = DELIM;        /* make sure not a space */
  1447.       strtok (c," ");        /* reset the strtok mechanism */
  1448.       *c = e;            /* put character back */
  1449.       break;
  1450.     }
  1451.   case '\0':            /* catch bogons */
  1452.   case ' ':
  1453.     return NIL;
  1454.   case '"':            /* quoted string */
  1455.     if (strchr (c+1,'"')) end = "\"";
  1456.     else return NIL;
  1457.   default:            /* atomic string */
  1458.     if (*d = strtok (c,end)) *n = strlen (*d);
  1459.     else return NIL;
  1460.     break;
  1461.   }
  1462.   return f;
  1463. }
  1464.